﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Amf4Asmx.AmfObjects;

namespace Amf4Asmx.IO.Writers
{
    internal class Amf0Writer : AmfBaseWriter
    {
        public Amf0Writer(BinaryWriter writer) : base(writer)
        {

        }

        protected override void WriteAmfHeader(AmfHeader header)
        {
            WriteUTF(header.Name);
            writer.Write(header.MustUnderstand);
            writer.Write(-1);
            WriteContent(header.Content);
        }

        protected override void WriteAmfBody(AmfBody body)
        {
            WriteUTF(body.Target ?? "null");
            WriteUTF(body.Response ?? "null");
            writer.Write(-1);
            WriteContent(body.Content);
        }



        protected virtual void WriteContent(object p)
        {
            if (p == null)
            {
                writer.Write((byte)Amf0TypeCode.Null);
                return;
            }

            if (p.GetType().IsPrimitive || p is string)
            {
                WritePrimitive(p);
            }
            else if (p.GetType().IsEnum)
            {
                WritePrimitive(p.ToString());
            }
            else if (p is System.Xml.XmlNode)
            {
                writer.Write((byte)Amf0TypeCode.Xml);
                WriteLongUTF((p as System.Xml.XmlNode).OuterXml);
            }
            else if (p.GetType().IsArray)
            {
                writer.Write((byte)Amf0TypeCode.Array);
                writer.Write((p as Array).Length);
                for (int i = 0; i < (p as Array).Length; i++)
                {
                    WriteContent((p as Array).GetValue(i));
                }
            }
            else if (p is NamedDictionary)
            {
                if ((p as NamedDictionary).TypeName == null)
                {
                    writer.Write((byte)Amf0TypeCode.ASObject);
                }
                else
                {
                    writer.Write((byte)Amf0TypeCode.CustomClass);
                    WriteUTF((p as NamedDictionary).TypeName);
                }
                foreach (KeyValuePair<string, object> kvp in (p as NamedDictionary).Properties)
                {
                    WriteUTF(kvp.Key);
                    WriteContent(kvp.Value);
                }
                WriteUTF("");
                writer.Write((byte)Amf0TypeCode.EndOfObject);
            }
            else
            {
                WriteContent(Amf4Asmx.Web.TypeHelper.GetNamedDictionaryFor(p));
            }
        }


        private void WritePrimitive(object p)
        {
            switch (Type.GetTypeCode(p.GetType()))
            {
                case TypeCode.Boolean:
                    WriteAmfBoolean(Convert.ToBoolean(p));
                    return;
                case TypeCode.Char:
                    WriteAmfString(p.ToString());
                    return;
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    WriteAmfString(p.ToString());
                    return;
                case TypeCode.SByte:
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Single:
                case TypeCode.Double:
                case TypeCode.Decimal:
                    writer.Write((byte)Amf0TypeCode.Number);
                    writer.Write(Convert.ToDouble(p));
                    return;
                case TypeCode.DateTime:
                    WriteAmfDateTime((DateTime)p);
                    return;
                case TypeCode.String:
                    WriteAmfString(p as string);
                    return;
            }
            throw new InvalidOperationException();
        }

        #region Primitive Writers

        private void WriteAmfBoolean(bool value)
        {
            writer.Write((byte)Amf0TypeCode.Boolean);
            writer.Write(value);
        }

        private void WriteAmfDateTime(DateTime value)
        {
            writer.Write((byte)Amf0TypeCode.DateTime);

            // Write date (milliseconds from 1970).
            DateTime timeStart = new DateTime(1970, 1, 1);
            TimeSpan span = value.Subtract(timeStart);
            long milliSeconds = (long)span.TotalMilliseconds;
            writer.Write((double)milliSeconds);
            span = TimeZone.CurrentTimeZone.GetUtcOffset(value);
            //whatever we write back, it is ignored
            //this.WriteLong(span.TotalMinutes);
            //this.WriteShort((int)span.TotalHours);
            //this.WriteShort(65236);
            writer.Write((short)(span.TotalMinutes));
        }

        private void WriteAmfString(string value)
        {
            UTF8Encoding utf8Encoding = new UTF8Encoding(true, true);
            int byteCount = utf8Encoding.GetByteCount(value);
            if (byteCount < ushort.MaxValue)
            {
                writer.Write((byte)Amf0TypeCode.String);
                WriteUTF(value);
            }
            else
            {
                writer.Write((byte)Amf0TypeCode.LongString);
                WriteLongUTF(value);
            }
        }

        private void WriteUTF(string p)
        {
            UTF8Encoding utf8Encoding = new UTF8Encoding();
            ushort byteCount = (ushort)utf8Encoding.GetByteCount(p);
            byte[] buffer = utf8Encoding.GetBytes(p);
            writer.Write((ushort)byteCount);
            if (buffer.Length > 0)
                writer.Write(buffer);
        }

        private void WriteLongUTF(string p)
        {
            UTF8Encoding utf8Encoding = new UTF8Encoding();
            uint byteCount = (uint)utf8Encoding.GetByteCount(p);
            byte[] buffer = utf8Encoding.GetBytes(p);
            writer.Write(byteCount);
            if (buffer.Length > 0)
                writer.Write(buffer);
        }

        #endregion
    }
}
